import React, {memo, useCallback, useRef, useState} from 'react';

const HYIncrement = memo((props) => {
  const { increment } = props;
  console.log("HYIncrement渲染");
  return (
      <div>
        <button onClick={increment}>increment+1</button>
        {/*  100个子组件 */}
      </div>
  )
})

/**
 * useCallback 会返回一个函数的记忆值，在依赖不变的情况下，多次定义的时候，返回的值是相同的
 * 当需要将一个函数传递给子组件时，最好使用useCallback进行优化，然后将优化后的函数传递给子组件
 */
//useCallback会返回一个函数的记忆值，在依赖不变的情况下，多次定义的时候，返回的值是相同的
//1.当需要将一个函数传递给子组件时，最好使用useCallback进行优化，将优化后的函数，传递给子组件
const App = memo(function () {
  const [counter, setCounter] = useState(0);
  const [message, setMessage] = useState('Hello World')

  //当前函数式组件每次重新渲染时都会进行调用，而当前方法每次都会被定义一次
  //根据函数对象的内存管理(js高级)，如果没有引用指针，每次重新定义时原方法都会被销毁(element被销毁，它所指包含的props中的方法自然也就不存在引用指针了)
  // function increment() {
  //   setCounter(counter + 1);
  // }

  //useCallback可以做到当前函数式组件多次执行时，每次返回的都是同一个函数
  //但是，当页面发生改变时，函数式组件重新渲染执行时，useCallback 传入的函数依然是在重新定义函数...因此在定义这一步不存在性能优化
  const increment = useCallback(function () {
    console.log("increment");
    setCounter(counter + 1);
    //接受第二个参数, 声明哪些依赖改变时,重新传入新的函数来更新内部函数
    //传入新声明的函数后，内部使用的就是新的counter值了
  }, [counter])
  /**
   * 优化点1：
   * 那么 useCallback 有什么价值呢？
   * 答：当该函数需要传入到一个子组件的时候，其作用就极其凸显了！
   * 普通函数: 每一次重新渲染父组件时, 该函数都会被重新定义, 因此依赖该函数的子组件都会被重新渲染
   * (因为每次传入的increment2都是一个新声明的函数, 代表子组件的props发生改变)
   * 但是当使用useCallback时：只要依赖的变量不发生改变, 每次传入子组件中的函数都是同一个，
   * 其它state发生改变不会使得子组件重新渲染.
   *
   * 当然前提是子组件使用memo包裹, 如果没有使用memo, 则无论怎样, 父组件发生改变时, 子组件都会跟着重新渲染.
   */

  /**
   * 优化点2：
   * 当前: useCallback依赖值(counter)发生改变时, 返回新函数, 这时子组件依赖发生改变，子组件重新渲染
   * 优化: 即使counter发生改变时，也不需要返回新的increment函数，因为执行代码是一样的，也就不需要重新渲染子组件
  */
  //做法1: 将counter移除掉 (×)
  const increment3 = useCallback(() => {
    //如果不传入第二个参数，会存在的问题：
    //第一次传入的声明函数是有效的，后续传入的声明函数不会被使用。但是，当外部数据改变时，第一次传入的声明函数中的变量不会改变！
    //闭包陷阱：函数中的变量值被内部函数的被捕获后，不再与外部函数中的变量有关系, 因此当函数声明未发生改变时, 获取到的counter依然是0
    setCounter(counter + 1);
  }, []);

  //做法2: 使用 useRef, ( useRef 在组件多次渲染时，返回的是同一个值 )
  const countRef = useRef();
  countRef.current = counter; //将 countRef 中的值赋值为最新的 counter 值
  const increment4 = useCallback(() => {
    //在当前组件重新渲染时，每次都会给 countRef 赋最新的 counter 值
    //而将 countRef 传入闭包函数中后，由于 countRef 是一个地址值，所以它本身并没有发生变化，而地址中的值已经被改变
    //所以即使 countRef 被内部函数捕获，依然不影响获取最新的 counter 值, useCallback也就不需要添加 counter 依赖了
    setCounter(countRef.current + 1);
  }, []);

  return (
      <div>
        <h2>当前计数：{counter}</h2>
        <h2>{message}</h2>
        <button onClick={increment4}>+1</button>
        <button onClick={() => setMessage( Math.random().toString())}>改变文字</button>
        <HYIncrement
            // increment={increment2}
            increment={increment4}
        />
      </div>
  );
})


/*
//函数闭包：
function foo(name) {
  function bar() {
    //仅在第一次使用上层函数中的name(捕获), 之后这个name就属于内部函数了, 与外部函数中的name无关
    console.log(name);
  }
  return bar;
}

const bar1 = foo('hello');
bar1(); //hello

const bar2 = foo('world');
bar2(); //world

bar1(); //打印什么？ 答案：hello
*/

export default App;